package com.cskaoyan.wordmemorize.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cskaoyan.wordmemorize.auth.StpKit;
import com.cskaoyan.wordmemorize.common.constant.CommonConstant;
import com.cskaoyan.wordmemorize.common.constant.ResultCodeEnum;
import com.cskaoyan.wordmemorize.common.exception.BusinessException;
import com.cskaoyan.wordmemorize.converter.ReviewConverter;
import com.cskaoyan.wordmemorize.dao.entity.*;
import com.cskaoyan.wordmemorize.dao.mapper.*;
import com.cskaoyan.wordmemorize.dto.admin.PageDTO;
import com.cskaoyan.wordmemorize.dto.app.NextLevelDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinStatisticsDTO;
import com.cskaoyan.wordmemorize.request.PageRequest;
import com.cskaoyan.wordmemorize.request.UserCheckinCommand;
import com.cskaoyan.wordmemorize.service.UserCheckinService;
import com.cskaoyan.wordmemorize.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserCheckinServiceImpl implements UserCheckinService {

    @Resource
    UserCheckinMapper userCheckinMapper;
    @Resource
    UserCheckinStatisticsMapper userCheckinStatisticsMapper;
    @Resource
    ReviewConverter reviewConverter;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserVocBoundStatisticsMapper userVocBoundStatisticsMapper;

    @Autowired
    private UserVocBoundLogMapper userVocBoundLogMapper;

    @Autowired
    private UserRevStatisticsMapper userRevStatisticsMapper;

    @Autowired
    private UserVocRevMapper userVocRevMapper;

    @Autowired
    private UserService userService;

    @Override
    public PageDTO<UserCheckinDTO> getCheckInData(PageRequest pageRequest, Long userId) {
        LambdaQueryWrapper<UserCheckinDO> userCheckinDOLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinDOLambdaQueryWrapper.eq(UserCheckinDO::getUserId, userId);
        //获得满足条件的数量
        Long l = userCheckinMapper.selectCount(userCheckinDOLambdaQueryWrapper);

        //分页查询
        if (pageRequest.getPageNum() == null) {
            List<UserCheckinDO> userCheckinDOS = userCheckinMapper.selectList(userCheckinDOLambdaQueryWrapper);
            PageDTO<UserCheckinDTO> userCheckinDTOPageDTO = reviewConverter.userCheckinPage2PageDTO(userCheckinDOS, l);
            return userCheckinDTOPageDTO;
        }
        //存在分页
        Page<UserCheckinDO> page = new Page<>(pageRequest.getPageNum(), pageRequest.getCount());
        Page<UserCheckinDO> pageSelect = userCheckinMapper.selectPage(page, userCheckinDOLambdaQueryWrapper);
        List<UserCheckinDO> records = pageSelect.getRecords();
        long total = page.getTotal();
        PageDTO<UserCheckinDTO> userCheckinDTOPageDTO = reviewConverter.userCheckinPage2PageDTO(records, total);

        return userCheckinDTOPageDTO;
    }


    @Transactional
    @Override
    public UserCheckinStatisticsDTO checkin(UserCheckinCommand command) {
        System.out.println("command = " + command);
        LambdaQueryWrapper<UserCheckinDO> userCheckinDOQueryWrapper = new LambdaQueryWrapper<>();
        LocalDate today = command.getSignDate();
        // 判断签到日期是不是今天
        if (!today.equals(LocalDate.now())) {
            throw new BusinessException(ResultCodeEnum.CHECK_IN_DATE_NOT_TODAY);
        }

        // 必须复习完今天的单词才能签到
        LambdaQueryWrapper<UserRevStatisticsDO> userRevStatisticsDOQueryWrapper = new LambdaQueryWrapper<>();
        userRevStatisticsDOQueryWrapper.eq(UserRevStatisticsDO::getUserId, command.getUserId())
                .eq(UserRevStatisticsDO::getReviewDate, today);
        UserRevStatisticsDO userRevStatisticsDO = userRevStatisticsMapper.selectOne(userRevStatisticsDOQueryWrapper);

        LambdaQueryWrapper<UserVocRevDO> userVocRevDOQueryWrapper = new LambdaQueryWrapper<>();
        userVocRevDOQueryWrapper.eq(UserVocRevDO::getUserId, command.getUserId())
                .eq(UserVocRevDO::getNextReviewDate, today);
        List<UserVocRevDO> userVocRevDOS = userVocRevMapper.selectList(userVocRevDOQueryWrapper);


        if (!userVocRevDOS.isEmpty() && userRevStatisticsDO == null) {
            System.out.println("不能签到");
            throw new BusinessException(ResultCodeEnum.USER_NO_REVIEW_VOC);
        }


        // 判断今日是否已经签过到（在user_checkin表中判断是否存在该用户今天的签到数据）
        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, command.getUserId())
                .eq(UserCheckinDO::getSignDate, today);
        UserCheckinDO todayCheckinDO = userCheckinMapper.selectOne(userCheckinDOQueryWrapper);
        if (todayCheckinDO != null) {
            throw new BusinessException(ResultCodeEnum.CHECK_IN_DATE_DUPLICATE);
        }
        // 获取UserCheckinDO对象
        UserCheckinDO userCheckinDO = reviewConverter.userCheckinCommand2DO(command);
        System.out.println("userCheckinDO = " + userCheckinDO);
        //  签到类型为正常签到
        userCheckinDO.setType(CommonConstant.USER_CHECK_IN_NORMAL_TYPE);
        // 签到日期肯定为今天
        userCheckinDO.setSignDate(today);
        // 在插入签到信息之前获取最后的签到日期
        LocalDate finalCheckinDate = userService.getFinalCheckinDate(StpKit.USER.getLoginIdAsLong());
        System.out.println("最后签到日期finalCheckinDate = " + finalCheckinDate);

        // 将签到数据加入到user_checkin表中
        userCheckinDO.setStickNo(-1L);
        int insert = userCheckinMapper.insert(userCheckinDO);
        if (insert == 0) {
            throw new BusinessException(ResultCodeEnum.FAIL);
        }
        Long stickNo = userCheckinDO.getId();

        // 更新user_checkin_statistics表数据
        // 因为每创建一个账号，user_checkin_statistics就会自动增加一条数据，所以不用考虑该用户签到统计数据不存在的情况
        // 在该表中，一个用户对应一条签到统计数据
        // 先获取到表中的数据
        QueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsDOQueryWrapper = new QueryWrapper<>();
        userCheckinStatisticsDOQueryWrapper.eq("user_id", command.getUserId());
        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsDOQueryWrapper);
        // 总签到天数 + 1
        userCheckinStatisticsDO.setTotalDays(userCheckinStatisticsDO.getTotalDays() + 1);

        // 执行到这里说明今天肯定没有签过到，现在是需要判断该用户是否是连续签到

        if (finalCheckinDate != null && finalCheckinDate.equals(today.minusDays(1))) {
            System.out.println("进入到连续签到");
            //-------------------------------------user_checkin------------------------------------------//
            // 进入到这里说明该用户最后的签到日期是昨天，所以肯定是连续签到
            userCheckinDOQueryWrapper.clear();
            // 查询出昨天的签到记录
            userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, command.getUserId())
                    .eq(UserCheckinDO::getSignDate, finalCheckinDate);

            UserCheckinDO yesterdayCheckinDO = userCheckinMapper.selectOne(userCheckinDOQueryWrapper);
            // 获取到昨日的签到段号
            stickNo = yesterdayCheckinDO.getStickNo();
            userCheckinDO.setStickNo(stickNo);


            //-------------------------------------user_checkin_statistics--------------------------------------//
            // 因为是连续签到，所以当前连续签到天数需要 + 1
            userCheckinStatisticsDO.setCurContinuousDays(userCheckinStatisticsDO.getCurContinuousDays() + 1);
            if (userCheckinStatisticsDO.getCurContinuousDays() > userCheckinStatisticsDO.getMaxContinuousDays()) {
                //如果当前连续签到天数 > 历史最大连续签到天数，就需要修改历史最大连续签到天数（相当于破新纪录了）
                userCheckinStatisticsDO.setMaxContinuousDays(userCheckinStatisticsDO.getCurContinuousDays());
                // 历史最大连续签到的开始日期 也需要更新为 当前连续签到的开始日期
                userCheckinStatisticsDO.setMaxContinuousStartDate(userCheckinStatisticsDO.getCurContinuousStartDate());
            }

        } else {
            // 如果进入else判断，则说明：
            // <1> finalCheckinDate == null，该用户从未签过到，这是他第一次签到
            // <2> 该用户最后的签到日期不是昨天，所以肯定不是连续签到
            // <3> 如果最后的签到日期不是昨天或者第一次签到的话，表中的cur_continuous_days为0且cur_continuous_start_date为null

            System.out.println("用户签到id = " + stickNo);
            userCheckinDO.setStickNo(stickNo);

            // 因为此时cur_continuous_days为0，cur_continuous_start_date为null，所以天数需要更新为1，日期更新为今天
            userCheckinStatisticsDO.setCurContinuousDays(1);
            userCheckinStatisticsDO.setCurContinuousStartDate(today);
            userCheckinStatisticsDO.setMaxContinuousDays(1);
            userCheckinStatisticsDO.setMaxContinuousStartDate(today);

        }

        // 更新user_checkin表数据，主要是更新签到段号stick_no
        userCheckinMapper.updateById(userCheckinDO);

        // 更新user_checkin_statistics表数据
        userCheckinStatisticsMapper.updateById(userCheckinStatisticsDO);

        // 更新单词上限
        updateVocBound(userCheckinDO.getUserId(), today);

        // 判断是否满足升级
        boolean flag = isCheckinUpgraded(userCheckinDO.getUserId(), today);
        if (flag){
            LambdaUpdateWrapper<UserDO> userDOUpdateWrapper = new LambdaUpdateWrapper<>();
            userDOUpdateWrapper.eq(UserDO::getId, userCheckinDO.getUserId())
                    .set(UserDO::getLevelDate, today);
            userMapper.update(userDOUpdateWrapper);
        }

        return reviewConverter.userCheckinStatisticsDO2DTO(userCheckinStatisticsDO);
    }

    private boolean isCheckinUpgraded(Long userId, LocalDate today) {
        // 在签到过后还需要判断是否满足升级条件（可以使用 返回用户的升级信息 方法来判断此用户还剩多少天升级）
        UserDO userDO = userMapper.selectById(userId);
        // 获取用户的等级信息
        Integer currentLevelValue = userDO.getLevelValue();
        System.out.println("我现在 " + currentLevelValue + " 级");

        NextLevelDTO userNextDTO = userService.getUserNextDTO(userId);
        boolean flag = false;   //判断是否满足升级
        while (currentLevelValue < CommonConstant.CHECK_IN_TYPE_MAX_LEVEL && (currentLevelValue == 0 || userNextDTO.getRemainDays() <= 0)) {
            // 说明满足升级条件了
            currentLevelValue++;
            System.out.println("还剩下" + userNextDTO.getRemainDays() + "天");

            System.out.println("我要升到" + currentLevelValue + "级");

            userDO.setLevelValue(currentLevelValue);
            userDO.setLevelName("LVL" + currentLevelValue);
            userDO.setLevelDate(today);

            // 因为存在跳级的情况，所以要先在数据库中更新信息，然后再去获取升级后的升级信息
            userMapper.updateById(userDO);
            userNextDTO = userService.getUserNextDTO(userId);
            flag = true;
        }
        return flag;
    }

    /**
     * 更新单词上限
     *
     * @param userId
     * @param today
     */
    private void updateVocBound(Long userId, LocalDate today) {
        LambdaQueryWrapper<UserVocBoundStatisticsDO> userVocBoundStatisticsDOQueryWrapper = new LambdaQueryWrapper<>();
        userVocBoundStatisticsDOQueryWrapper.eq(UserVocBoundStatisticsDO::getUserId, userId);
        UserVocBoundStatisticsDO userVocBoundStatisticsDO = userVocBoundStatisticsMapper.selectOne(userVocBoundStatisticsDOQueryWrapper);

        LambdaUpdateWrapper<UserVocBoundStatisticsDO> userVocBoundStatisticsDOUpdateWrapper = new LambdaUpdateWrapper<>();
        userVocBoundStatisticsDOUpdateWrapper.eq(UserVocBoundStatisticsDO::getUserId, userId)
                .set(UserVocBoundStatisticsDO::getTotal, userVocBoundStatisticsDO.getTotal() + CommonConstant.USER_CHECKIN_TYPE_CONTINUOUS)
                .set(UserVocBoundStatisticsDO::getAvailable, userVocBoundStatisticsDO.getAvailable() + CommonConstant.USER_CHECKIN_TYPE_CONTINUOUS)
                .set(UserVocBoundStatisticsDO::getFree, userVocBoundStatisticsDO.getFree() + CommonConstant.USER_CHECKIN_TYPE_CONTINUOUS);

        int updateVoc = userVocBoundStatisticsMapper.update(userVocBoundStatisticsDOUpdateWrapper);

        UserVocBoundLogDO build = UserVocBoundLogDO.builder()
                .userId(userId)
                .paySaleId(null)
                .type(CommonConstant.USER_VOC_BOUND_CHANGE_TYPE_CHECKIN_REWARD)
                .logDate(today)
                .count(CommonConstant.USER_CHECKIN_TYPE_CONTINUOUS)
                .description(CommonConstant.USER_VOC_BOUND_DESCRIPTION_CHECKIN)
                .build();

        int insertVoc = userVocBoundLogMapper.insert(build);

        if (insertVoc == 0 || updateVoc == 0) {
            throw new BusinessException(ResultCodeEnum.FAIL);
        }
    }



    @Override

    public UserCheckinStatisticsDTO getUserCheckinStatistics(Long userId) {
        if (userId <= 0) {
            return null;
        }

        LambdaQueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsDOLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinStatisticsDOLambdaQueryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsDOLambdaQueryWrapper);
        if (userCheckinStatisticsDO == null) {
            return null;
        }
        return reviewConverter.userCheckinStatisticsDO2DTO(userCheckinStatisticsDO);

    }


    @Override
    public UserCheckinStatisticsDTO getUserContinuousCheckinInfo(Long userId) {

        return null;
    }

    @Transactional
    @Override
    public void userResignCard(Long userId) {
        UserDO userDO = userMapper.selectById(userId);
        Integer hasSignCard = userDO.getHasSignCard();
        if (hasSignCard == 0) {
            throw new BusinessException(ResultCodeEnum.USER_NO_RESIGN_CARD);
        }

        LocalDate today = LocalDate.now();

        LambdaQueryWrapper<UserCheckinDO> userCheckinDOQueryWrapper = new LambdaQueryWrapper<>();
        // 判断今日是否已经签过到（在user_checkin表中判断是否存在该用户今天的签到数据），今日必须先签到，才能补签
        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, today);
        UserCheckinDO todayCheckinDO = userCheckinMapper.selectOne(userCheckinDOQueryWrapper);
        if (todayCheckinDO == null) {
            throw new BusinessException(ResultCodeEnum.USER_CHECKIN_DATA_NEED_SYNC);
        }

        // 计算理想补签日期
        //理想补签日期 = 当前连续签到起始日期 - (历史最大连续签到天数 - 当前连续签到天数)
        //当用户的历史连续签到天数大于当前连续签到天数时，就可以使用补签卡，否则不能使用
        LambdaQueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsDOQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinStatisticsDOQueryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsDOQueryWrapper);

        // 获取用户历史最大连续签到天数
        Integer maxContinuousDays = userCheckinStatisticsDO.getMaxContinuousDays();
        // 获取用户当前连续签到天数
        Integer curContinuousDays = userCheckinStatisticsDO.getCurContinuousDays();

        if (maxContinuousDays <= curContinuousDays) {
            throw new BusinessException(ResultCodeEnum.FAIL_RECHECKIN);
        }

        // 获取用户当前连续签到起始日期
        LocalDate curContinuousStartDate = userCheckinStatisticsDO.getCurContinuousStartDate();

        LocalDate idealCheckinDate = curContinuousStartDate.minusDays(maxContinuousDays - curContinuousDays);

        userCheckinDOQueryWrapper.clear();

        /*  如果理想补签日期有签到记录，则将理想签到日期改为它所在的连续签到段的最后一次签到的下一天，
            然后在理想签到日期到当前连续签到起始日期之前的每一天，有签到记录就修改段号为当前签到段段号，
            如果没有，则插入签到记录签到段号也为当前签到段段号
        */

        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, idealCheckinDate);
        UserCheckinDO idealCheckinDO = userCheckinMapper.selectOne(userCheckinDOQueryWrapper);
        if (idealCheckinDO != null) {
            userCheckinDOQueryWrapper.clear();
            // 不为null，则说明理想补签日期这天有签到记录，需要理想签到日期改为它所在的连续签到段的最后一次签到的下一天
            Long stickNo = idealCheckinDO.getStickNo();
            userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                    .eq(UserCheckinDO::getStickNo, stickNo)
                    .orderByDesc(true, UserCheckinDO::getSignDate).last("LIMIT 1");
            // 计算理想补签日期
            idealCheckinDate = userCheckinMapper.selectOne(userCheckinDOQueryWrapper).getSignDate().plusDays(1);
        }

        System.out.println("理想补签日期 = " + idealCheckinDate);

        userCheckinDOQueryWrapper.clear();
        // 获取到该用户 从理想补签日期 直到 当前连续签到起始日期前一天的这段时间内的签到数据
        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .between(UserCheckinDO::getSignDate, idealCheckinDate, curContinuousStartDate.minusDays(1));
        List<UserCheckinDO> userCheckinDOS = userCheckinMapper.selectList(userCheckinDOQueryWrapper);
        List<LocalDate> dateList = userCheckinDOS.stream().map(UserCheckinDO::getSignDate).collect(Collectors.toList());

        int checkinDays = 0;//计算从理想补签日期 直到 当前连续签到起始日期前一天 的这段时间内的补签天数
        LocalDate tempIdealCheckinDate = idealCheckinDate;
        while (tempIdealCheckinDate.isBefore(curContinuousStartDate)) {
            int index = dateList.indexOf(tempIdealCheckinDate);

            if (index == -1) {
                UserCheckinDO userCheckinDO = new UserCheckinDO();
                // 进入到这里，说明从理想补签日期 直到 当前连续签到起始日期前一天的这段时间内的这个日期没有打卡数据
                checkinDays++;

                userCheckinDO.setUserId(userId);
                userCheckinDO.setStickNo(todayCheckinDO.getStickNo());
                // 签到类型为补签
                userCheckinDO.setType(2);
                userCheckinDO.setSignDate(tempIdealCheckinDate);
                userCheckinMapper.insertOrUpdate(userCheckinDO);
            } else {
                UserCheckinDO userCheckinDO = userCheckinDOS.get(index);
                userCheckinDO.setStickNo(todayCheckinDO.getStickNo());
                userCheckinMapper.insertOrUpdate(userCheckinDO);
            }

            tempIdealCheckinDate = tempIdealCheckinDate.plusDays(1);
        }

        // 更新user_checkin_statistics表中的数据
        userCheckinStatisticsDO.setTotalDays(userCheckinStatisticsDO.getTotalDays() + checkinDays);
        long daysBetween = ChronoUnit.DAYS.between(idealCheckinDate, curContinuousStartDate);
        userCheckinStatisticsDO.setCurContinuousDays(userCheckinStatisticsDO.getCurContinuousDays() + (int) daysBetween);
        userCheckinStatisticsDO.setCurContinuousStartDate(idealCheckinDate);

        userCheckinStatisticsMapper.updateById(userCheckinStatisticsDO);


        // 用户的补签卡变为0
        userDO.setHasSignCard(0);
        userMapper.updateById(userDO);

        // 判断是否满足升级
        boolean flag = isUpgraded(userId);
        if (flag){
            LambdaUpdateWrapper<UserDO> userDOUpdateWrapper = new LambdaUpdateWrapper<>();
            userDOUpdateWrapper.eq(UserDO::getId,userId)
                    .set(UserDO::getLevelDate, today);
            userMapper.update(userDOUpdateWrapper);
        }
    }

    private boolean isUpgraded(Long userId) {
        // 在签到过后还需要判断是否满足升级条件（可以使用 返回用户的升级信息 方法来判断此用户还剩多少天升级）
        UserDO userDO = userMapper.selectById(userId);
        // 获取用户的等级信息
        Integer currentLevelValue = userDO.getLevelValue();
        System.out.println("我现在 " + currentLevelValue + " 级");

        NextLevelDTO userNextDTO = userService.getUserNextDTO(userId);
        boolean flag = false;   //判断是否满足升级
        while (currentLevelValue < CommonConstant.CHECK_IN_TYPE_MAX_LEVEL && (currentLevelValue == 0 || userNextDTO.getRemainDays() <= 0)) {
            // 说明满足升级条件了
            currentLevelValue++;
            System.out.println("还剩下" + userNextDTO.getRemainDays() + "天");

            System.out.println("我要升到" + currentLevelValue + "级");

            userDO.setLevelValue(currentLevelValue);
            userDO.setLevelName("LVL" + currentLevelValue);


            // 因为存在跳级的情况，所以要先在数据库中更新信息，然后再去获取升级后的升级信息
            userMapper.updateById(userDO);
            userNextDTO = userService.getUserNextDTO(userId);
            flag = true;
        }
        return flag;
    }

}
